package common.dal;

import java.sql.*;
import java.util.*;

import common.dal.SQLDataColumn.Constraints;
import common.exceptions.InvalidKeyException;
import common.interfaces.*;

/**
 * An abstract DAL implementation template
 * 
 * @author Gal Lalouche
 * @param <Key> The type of the key of the object being persisted
 * @param <T> The type of the object being persisted
 */
public abstract class AbstractDAL<T extends IHasId<Key>, Key> extends AbstractPersistent implements IDAL<T, Key> {
	
	/**
	 * Creates a new DAL
	 * 
	 * @param t The transaction all actions will act upon
	 */
	protected AbstractDAL(Transaction t) {
		super(t);
	}
	
	/**
	 * A template of the primary's column's name
	 * 
	 * @return The primary's column's name (with constraint {@link Constraints#PRIMARY_KEY})
	 */
	protected abstract String getPrimaryColumnName();
	
	/**
	 * A method that generates the type from an SQL result set. Assume next() has already been called.
	 * 
	 * @param result The result set
	 * @return The type as generated from the set
	 * @throws SQLException
	 */
	protected abstract T generateFromResultSet(ResultSet result) throws SQLException;
	
	private WhereEntry getWhereClause(Key key) {
		return new WhereEntry(getPrimaryColumnName(), WhereOperator.EQUALS, key.toString());
	}
	
	// /**
	// * Selects a given column from table
	// *
	// * @param columnName The column to select
	// * @return The select column from all the rows
	// * @throws SQLException
	// */
	// protected ResultSet selectColumn(String columnName) throws SQLException {
	// return selectColumn(columnName, null);
	// }
	//
	// /**
	// * Selects a given using a given key
	// *
	// * @param columnName The column to select
	// * @param key The key to match against
	// * @return The column in the row matching the key
	// * @throws SQLException
	// */
	// protected ResultSet selectColumn(String columnName, Key key) throws
	// SQLException {
	// openConnection();
	// return selectWhere(getWhereClause(key));
	// }
	
	/**
	 * Get all objects from a given result set
	 * 
	 * @param results The set to get the objects from
	 * @return All the objects from the set
	 * @throws SQLException
	 */
	protected Collection<T> getAllFromSet(ResultSet results) throws SQLException {
		if (results == null) {
			throw new IllegalArgumentException("results is null");
		}
		Collection<T> all = new LinkedList<>();
		while (results.next()) {
			all.add(generateFromResultSet(results));
		}
		return all;
	}
	
	/**
	 * Gets all results associated with the key
	 * 
	 * @param key They primary key identifying the row
	 * @return All the results with the key
	 * @throws SQLException
	 */
	protected ResultSet getInternal(Key key) throws SQLException {
		return selectWhere(new WhereEntry(getPrimaryColumnName(), WhereOperator.EQUALS, key));
	}
	
	@Override
	public boolean exists(Key key) throws SQLException {
		if (key == null) {
			throw new IllegalArgumentException("key is null");
		}
		boolean result = getInternal(key).first();
		return result;
	}
	
	@Override
	public boolean delete(Key key) throws SQLException {
		if (exists(key) == false) {
			return false;
		}
		deleteWhere(getWhereClause(key));
		return true;
	}
	
	@Override
	public T get(Key key) throws SQLException, InvalidKeyException {
		if (key == null) {
			throw new IllegalArgumentException("key is null");
		}
		
		try (ResultSet set = getInternal(key)) {
			if (set.first() == false) {
				throw new InvalidKeyException(key);
			}
			T result = generateFromResultSet(set);
			return result;
		}
	}
	
	@Override
	public Collection<T> getAll() throws SQLException {
		try (ResultSet results = selectAll()) {
			return getAllFromSet(results);
		}
	}
}
